package beautiful.butterfly.drds.datasource.connection_contexts.spi;

//import beautiful.butterfly.drds.datasource.author.Author;
//import beautiful.butterfly.drds.datasource.author.Authors;


import beautiful.butterfly.drds.datasource.connection_contexts.api.IConnectionContexts;
import beautiful.butterfly.drds.datasource.connection_contexts.spi.thread.CreateConnectionContextTask;
import beautiful.butterfly.drds.datasource.connection_contexts.spi.thread.CreateConnectionContextThread;
import beautiful.butterfly.drds.datasource.connection_contexts.spi.thread.DestroyConnectionContextTask;
import beautiful.butterfly.drds.datasource.connection_contexts.spi.thread.DestroyConnectionContextThread;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.sql.Connection;
import java.sql.SQLException;
import java.util.*;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.Future;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicLong;
import java.util.concurrent.locks.ReentrantLock;

//@Author(name = Authors.LI_YANG)
public class ConnectionContexts extends AbstractConnectionContexts implements IConnectionContexts {
    public static final Logger logger = LoggerFactory.getLogger(ConnectionContexts.class);

    static {
        logger.info("数据库连接池初始化");
    }


    public static ThreadLocal<Long> waitTimeNanos = new ThreadLocal<Long>();
    // stats
    public final AtomicLong recycleErrorCount = new AtomicLong();
    public final AtomicLong connecteExceptionCount = new AtomicLong();
    public final CountDownLatch countDownLatch = new CountDownLatch(2);

    public boolean killWhenSocketReadTimeout = false;
    public long connectCount = 0L;
    public long closeCount = 0L;

    public long removeAbandonedCount = 0L;

    public long notEmptyWaitTimeNanos = 0L;
    public int keepAliveCheckCount = 0;

    /**
     * 最大值
     */
    public int poolingPeak = 0;
    public long poolingPeakTimeMillis = 0;
    // store
    public volatile SharedConnection[] poolingSharedConnections;
    public int poolingIndex = 0;
    public int activeCount = 0;
    public long discardCount = 0;
    public int notEmptyWaitThreadCount = 0;
    public int notEmptyWaitThreadCountPeak = 0;
    //
    public SharedConnection[] evictSharedConnections;
    public SharedConnection[] keepAliveSharedConnections;
    // threads
    public volatile ScheduledFuture<?> destroySchedulerFuture;
    public DestroyConnectionContextTask destroyConnectionContextTask;
    public volatile Future<?> createConnectionTaskFuture;
    public CreateConnectionContextThread createConnectionContextThread;
    public DestroyConnectionContextThread destroyConnectionContextThread;
    public int creatingConnectionTaskCount;
    public volatile boolean enable = true;

    public volatile boolean closed = false;
    public long closeTimeMillis = -1L;
    public volatile boolean keepAlive = false;

    public ConnectionContexts() {
        super(false);
    }

    //
    public void config(Properties properties) {

        {
            this.setUrl(properties.getProperty(ConnectionContextsFactory.properties_url));
        }
        {
            this.setUsername(properties.getProperty(ConnectionContextsFactory.properties_username));
        }
        {
            this.setPassword(properties.getProperty(ConnectionContextsFactory.properties_password));
        }
        {
            this.setTestOnBorrow(getBoolean(properties, ConnectionContextsFactory.properties_test_on_borrow));
        }
        {
            this.setTestWhileIdle(getBoolean(properties, ConnectionContextsFactory.properties_test_while_idle));
        }

        {
            String property = properties.getProperty(ConnectionContextsFactory.properties_not_full_timeout_retry_count);
            if (property != null && property.length() > 0) {
                int value = Integer.parseInt(property);
                this.setNotFullTimeoutRetryCount(value);
            }
        }
        {
            String property = properties.getProperty(ConnectionContextsFactory.properties_max_wait_not_empty_thread_count);
            if (property != null && property.length() > 0) {
                int value = Integer.parseInt(property);
                this.setMaxWaitNotEmptyThreadCount(value);
            }
        }
        {
            Boolean value = getBoolean(properties, ConnectionContextsFactory.properties_fail_fast);
            if (value != null) {
                this.setFailFast(value);
            }
        }
        {
            String property = properties.getProperty(ConnectionContextsFactory.properties_max_active_timeout_timemillis);
            if (property != null && property.length() > 0) {
                long value = Long.parseLong(property);
                this.setMaxAliveTimeOutTimeMillis(value);
            }
        }
        {
            String property = properties.getProperty(ConnectionContextsFactory.properties_min_evictable_idle_timemillis);
            if (property != null && property.length() > 0) {
                long value = Long.parseLong(property);
                this.setMinEvictableIdleTimeMillis(value);
            }
        }
        {
            String property = properties.getProperty(ConnectionContextsFactory.properties_max_evictable_idle_timemillis);
            if (property != null && property.length() > 0) {
                long value = Long.parseLong(property);
                this.setMaxEvictableIdleTimeMillis(value);
            }
        }
        {
            Boolean value = getBoolean(properties, ConnectionContextsFactory.properties_keep_alive);
            if (value != null) {
                this.setKeepAlive(value);
            }
        }
        {
            Boolean value = getBoolean(properties, ConnectionContextsFactory.properties_use_unfair_lock);
            if (value != null) {
                this.setUseUnfairLock(value);
            }
        }
        {
            String property = properties.getProperty(ConnectionContextsFactory.properties_initial_size);
            if (property != null && property.length() > 0) {
                int value = Integer.parseInt(property);
                this.setInitialSize(value);
            }
        }
        {
            String property = properties.getProperty(ConnectionContextsFactory.properties_min_idle);
            if (property != null && property.length() > 0) {
                int value = Integer.parseInt(property);
                this.setMinIdle(value);
            }
        }
        {
            String property = properties.getProperty(ConnectionContextsFactory.properties_max_active);
            if (property != null && property.length() > 0) {
                int value = Integer.parseInt(property);
                this.setMaxActive(value);
            }
        }
        {
            Boolean value = getBoolean(properties, ConnectionContextsFactory.properties_kill_when_socket_read_timeout);
            if (value != null) {
                setKillWhenSocketReadTimeout(value);
            }
        }
        {

            if (properties.getProperty("properties") != null) {
                this.setConnectionProperties(properties.getProperty("properties"));
            }
        }
    }

    public void setKillWhenSocketReadTimeout(boolean killWhenSocketTimeOut) {
        this.killWhenSocketReadTimeout = killWhenSocketTimeOut;
    }

    public boolean isKillWhenSocketReadTimeout() {
        return killWhenSocketReadTimeout;
    }

    public void setKeepAlive(boolean keepAlive) {
        this.keepAlive = keepAlive;
    }

    public boolean isKeepAlive() {
        return keepAlive;
    }

    //
    public void setEnable(boolean enable) {
        lock.lock();
        try {
            this.enable = enable;
            if (!enable) {
                this.notEmpty.signalAll();

            }
        } finally {
            lock.unlock();
        }
    }

    public boolean isEnable() {
        return enable;
    }

    //
    public void setKeepAliveCheckCount(int keepAliveCheckCount) {
        this.keepAliveCheckCount = keepAliveCheckCount;
    }

    public int getKeepAliveCheckCount() {
        return keepAliveCheckCount;
    }

    @SuppressWarnings("")
    public void restart() throws SQLException {
        this.lock.lock();
        try {
            if (this.activeCount > 0) {
                throw new SQLException("can not restart, activeCount not connection_contexts. " + activeCount);
            }

            this.close();
            this.reset();
            this.inited = false;
            this.enable = true;
            this.closed = false;
        } finally {
            this.lock.unlock();
        }
    }

    public void reset() {

        this.lock.lock();
        try {
            this.connectCount = 0;
            this.closeCount = 0;
            this.discardCount = 0;

            this.createCount.set(0);
            this.destroyConnectionCount.set(0);
            this.removeAbandonedCount = 0;

            this.notEmptyWaitTimeNanos = 0;

            this.poolingPeak = 0;

            this.lastError = null;
            this.lastErrorTimeMillis = 0;
            this.lastCreateConntectionException = null;
            this.lastCreateConntectionExceptionTimeMillis = 0;
        } finally {
            this.lock.unlock();
        }

        this.connecteExceptionCount.set(0);
        this.errorCount.set(0);
        this.recycleErrorCount.set(0);

    }

    //
    public void setMaxActive(int maxActive) {
        if (this.maxActive == maxActive) {
            return;
        }
        if (maxActive <= 0) {
            throw new IllegalArgumentException("maxActive can't not set connection_contexts");
        }
        if (!this.inited) {
            this.maxActive = maxActive;
            return;
        } else {
            //
            if (maxActive < this.minIdle) {
                throw new IllegalArgumentException("maxActive less than minIdle, " + maxActive + " < " + this.minIdle);
            }
            lock.lock();
            try {
                int count = this.poolingIndex + this.activeCount;
                if (maxActive > count) {// 传入参数比当前大
                    // 复制连接池中的连接
                    this.poolingSharedConnections = Arrays.copyOf(this.poolingSharedConnections, maxActive);
                    this.evictSharedConnections = new SharedConnection[maxActive];
                    this.keepAliveSharedConnections = new SharedConnection[maxActive];
                } else {
                    this.poolingSharedConnections = Arrays.copyOf(this.poolingSharedConnections, count);// count>=maxActive
                    // 则当前数组在完全存放maxActive个数据库连接的情况下还存在null
                    this.evictSharedConnections = new SharedConnection[count];
                    this.keepAliveSharedConnections = new SharedConnection[count];
                }
                this.maxActive = maxActive;
            } finally {
                this.lock.unlock();
            }
        }

    }

    /**
     * @param properties 新配置
     */
    @SuppressWarnings("rawtypes")
    public void setProperties(Properties properties) {
        if (properties == null) {
            properties = new Properties();
        }
        boolean equals;
        if (properties.size() == this.properties.size()) {
            equals = true;// 暂时相等
            for (Map.Entry entry : properties.entrySet()) {
                Object value = entry.getValue();// 新配置
                if (this.properties.get(entry.getKey()) == null && value != null) {
                    equals = false;
                    break;
                }
                if (!this.properties.get(entry.getKey()).equals(entry.getValue())) {
                    equals = false;
                    break;
                }
            }
        } else {
            equals = false;
        }
        if (!equals) {// 不等则需要重新配置
            config(properties);
        }
        this.properties = properties;
    }

    public void init() throws SQLException {
        if (this.inited) {
            return;
        }
        logger.info("创建数据库连接初始化程序启动");
        final ReentrantLock lock = this.lock;
        try {
            lock.lockInterruptibly();
        } catch (InterruptedException e) {
            throw new RuntimeException("interrupt", e);
        }

        try {
            if (this.inited) {
                return;
            }
            //
            if (this.maxActive <= 0) {
                throw new IllegalArgumentException("illegal maxActive " + maxActive);
            }

            if (this.maxActive < minIdle) {
                throw new IllegalArgumentException("illegal maxActive " + maxActive);
            }

            if (this.maxActive < this.initialSize) {
                throw new IllegalArgumentException("illegal initialSize " + this.initialSize + ", maxActive " + maxActive);
            }

            if (this.maxEvictableIdleTimeMillis < this.minEvictableIdleTimeMillis) {
                throw new SQLException("maxEvictableIdleTimeMillis must be grater than minEvictableIdleTimeMillis");
            }

            this.poolingSharedConnections = new SharedConnection[this.maxActive];
            this.evictSharedConnections = new SharedConnection[this.maxActive];
            this.keepAliveSharedConnections = new SharedConnection[this.maxActive];

            SQLException sqlException = null;

            try {
                // init poolingSharedConnections
                for (int i = 0, size = getInitialSize(); i < size; ++i) {
                    Connection connection = createConnection();
                    SharedConnection sharedConnection = new SharedConnection(this, connection);
                    logger.info("createConnection:" + sharedConnection);
                    this.poolingSharedConnections[this.poolingIndex] = sharedConnection;
                    incrementPoolingIndex();
                }

                if (this.poolingIndex > 0) {
                    this.poolingPeak = this.poolingIndex;
                    this.poolingPeakTimeMillis = System.currentTimeMillis();
                }
            } catch (SQLException ex) {
                logger.error("init datasource error, url: " + this.getUrl());
                sqlException = ex;
            }

            createCreateConnectionThread();
            createDestroyConnectionThread();

            this.countDownLatch.await();

            if (sqlException != null && this.poolingIndex == 0) {
                throw sqlException;
            }

            if (this.keepAlive) {
                // async fill to minIdle
                if (this.scheduledExecutorService != null) {
                    for (int i = 0; i < this.minIdle; ++i) {
                        this.creatingConnectionTaskCount++;
                        CreateConnectionContextTask createConnectionContextTask = new CreateConnectionContextTask(this);
                        this.createConnectionTaskFuture = this.scheduledExecutorService.submit(createConnectionContextTask);
                    }
                } else {
                    this.createConnectionTask();
                }
            }
        } catch (SQLException e) {
            logger.error(e.getMessage());
            throw e;
        } catch (InterruptedException e) {
            throw new RuntimeException(e.getMessage(), e);
        } catch (RuntimeException e) {
            logger.error(e.getMessage());
            throw e;
        } catch (Error e) {
            logger.error(e.getMessage());
            throw e;
        } finally {
            inited = true;
            lock.unlock();

        }
    }


    protected void createCreateConnectionThread() {
        if (this.scheduledExecutorService == null) {
            String threadName = "Create-Connection-Thread-" + System.identityHashCode(this);
            this.createConnectionContextThread = new CreateConnectionContextThread(this, threadName);
            this.createConnectionContextThread.start();
            return;
        } else {
            this.countDownLatch.countDown();
        }

    }

    protected void createDestroyConnectionThread() {
        this.destroyConnectionContextTask = new DestroyConnectionContextTask(this);

        if (this.scheduledExecutorService != null) {
            long period = this.timeBetweenEvictionRunsMillis;
            if (period <= 0) {
                period = 1000 * 1 / 10;
            }
            logger.info("启动连接消除程序");
            this.destroySchedulerFuture = this.scheduledExecutorService.scheduleAtFixedRate(destroyConnectionContextTask, period, period, TimeUnit.MILLISECONDS);
            this.countDownLatch.countDown();
            return;
        } else {
            String threadName = DestroyConnectionContextThread.class.getName() + System.identityHashCode(this);
            this.destroyConnectionContextThread = new DestroyConnectionContextThread(this, threadName);
            this.destroyConnectionContextThread.start();
        }

    }

    public ConnectionContext getConnectionContext() throws SQLException {
        return getConnectionContextWith(this.maxWaitTimeMillis);
    }

    public ConnectionContext getConnectionContextWith(long maxWaitTimeMillis) throws SQLException {
        init();
        return get(maxWaitTimeMillis);
    }

    public ConnectionContext get(long maxWaitTimeMillis) throws SQLException {
        logger.info("获取连接,最大等待时间" + maxWaitTimeMillis);
        int notFullTimeoutRetryCnt = 0;
        for (; ; ) {

            ConnectionContext connectionContext;
            try {
                connectionContext = getConnectionContext(maxWaitTimeMillis);
            } catch (SQLException ex) {
                if (notFullTimeoutRetryCnt <= this.notFullTimeoutRetryCount && !isFull()) {
                    notFullTimeoutRetryCnt++;
                    logger.warn("not full timeout retry : " + notFullTimeoutRetryCnt);
                    continue;
                } else {
                    throw ex;
                }

            }

            if (isTestOnBorrow()) {
                boolean validate = validateConnection(connectionContext.getConnection());
                if (!validate) {
                    Connection connection = connectionContext.getConnection();
                    discardConnection(connection);
                    continue;
                }
            } else {
                Connection connection = connectionContext.getConnection();
                if (connection.isClosed()) {
                    discardConnection(null); // 传入null，避免重复关闭
                    continue;
                }

                if (isTestWhileIdle()) {
                    final long currentTimeMillis = System.currentTimeMillis();
                    final long lastActiveTimeMillis = connectionContext.getSharedConnection().getLastActiveTimeMillis();
                    final long idleTimeMillis = currentTimeMillis - lastActiveTimeMillis;
                    long timeMillisBetweenEvictionRuns = this.getTimeBetweenEvictionRunsMillis();
                    if (timeMillisBetweenEvictionRuns <= 0) {
                        timeMillisBetweenEvictionRuns = default_time_millis_between_eviction_runs;
                    }

                    if (idleTimeMillis >= timeMillisBetweenEvictionRuns) {
                        boolean validate = validateConnection(connectionContext.getConnection());
                        if (!validate) {
                            logger.warn("skip not validate connection.");
                            discardConnection(connection);
                            continue;
                        }
                    }
                }
            }

            connectionContext.setConnectedTimeNano(System.nanoTime());
            connectionContext.setTraceEnable(true);

            this.activePooledConnectionToObjectMapLock.lock();
            try {
                this.activeConnectionContextToObjectMap.put(connectionContext, PRESENT);
            } finally {
                this.activePooledConnectionToObjectMapLock.unlock();
            }

            if (!this.isDefaultAutoCommit()) {
                connectionContext.setAutoCommit(false);
            }

            return connectionContext;
        }
    }


    /**
     * 回收连接
     */
    public void recycle(ConnectionContext connectionContext) {
        final SharedConnection sharedConnection = connectionContext.getSharedConnection();
        if (sharedConnection == null) {
            logger.warn("sharedConnection is null");
            return;
        }

        if ((connectionContext.getGetThread() != Thread.currentThread())) {
            logger.warn("get/close not same thread");
        }

        final Connection connection = sharedConnection.getConnection();
        if (connectionContext.isTraceEnable()) {
            Object object = null;
            this.activePooledConnectionToObjectMapLock.lock();
            try {
                if (connectionContext.isTraceEnable()) {
                    object = this.activeConnectionContextToObjectMap.remove(connectionContext);
                    connectionContext.setTraceEnable(false);
                }
            } finally {
                this.activePooledConnectionToObjectMapLock.unlock();
            }
            if (object == null) {
                logger.warn("removeSql abandonded failed. activeConnectionContextToObjectMap.size " + activeConnectionContextToObjectMap.size());
            }
        }
        logger.info("恢复默认值");
        final boolean isReadOnly = sharedConnection.isReadOnly();
        final boolean isAutoCommit = sharedConnection.isAutoCommit();
        final boolean testOnReturn = this.isTestOnReturn();

        try {
            // check need to rollback?
            if ((!isAutoCommit) && (!isReadOnly)) {
                try {
                    connectionContext.rollback();
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }

            // reset sharedConnection, restore default settings, clear warnings
            boolean isSameThread = connectionContext.getGetThread() == Thread.currentThread();
            if (!isSameThread) {
                final ReentrantLock lock = connectionContext.lock;
                lock.lock();
                try {
                    sharedConnection.reset();
                } finally {
                    lock.unlock();
                }
            } else {
                sharedConnection.reset();
            }

            if (sharedConnection.isDiscard()) {
                return;
            }

            if (testOnReturn) {
                boolean testOk = validateConnection(connection);
                if (!testOk) {
                    JdbcUtils.close(connection);
                    this.destroyConnectionCount.incrementAndGet();

                    this.lock.lock();
                    try {
                        this.activeCount--;
                        this.closeCount++;
                    } finally {
                        this.lock.unlock();
                    }
                    return;
                }
            }

            if (!this.enable) {
                discardConnection(sharedConnection.getConnection());
                return;
            }

            boolean result;
            final long lastActiveTimeMillis = System.currentTimeMillis();
            // lockInterruptibly的实现有如下问题:会导致当前线程被Interrupt时,holder无法正常被回收
            this.lock.lock();
            try {
                this.activeCount--;
                this.closeCount++;
                result = putLast(sharedConnection, lastActiveTimeMillis);

            } finally {
                this.lock.unlock();
            }

            if (!result) {
                JdbcUtils.close(sharedConnection.getConnection());

            }
        } catch (Throwable e) {

            if (!sharedConnection.isDiscard()) {
                this.discardConnection(connection);
                sharedConnection.setDiscard(true);
            }

            this.recycleErrorCount.incrementAndGet();
        }
    }

    /**
     * close datasource
     */
    public void close() {
        this.lock.lock();
        try {
            if (!this.inited) {
                return;
            }
            if (this.closed) {
                return;
            }
            //
            if (this.createConnectionContextThread != null) {
                this.createConnectionContextThread.interrupt();
            }
            if (this.createConnectionTaskFuture != null) {
                this.createConnectionTaskFuture.cancel(true);
            }
            //
            if (this.destroyConnectionContextThread != null) {
                this.destroyConnectionContextThread.interrupt();
            }
            if (this.destroySchedulerFuture != null) {
                this.destroySchedulerFuture.cancel(true);
            }
            //
            for (int i = 0; i < this.poolingIndex; ++i) {
                SharedConnection sharedConnection = this.poolingSharedConnections[i];
                if (sharedConnection == null) {
                    throw new RuntimeException("");
                }
                Connection connection = sharedConnection.getConnection();
                if (connection == null) {
                    throw new RuntimeException("");
                }
                try {
                    connection.close();
                } catch (Exception ex) {
                    logger.error("close connection error", ex);
                }
                this.poolingSharedConnections[i] = null;
                this.destroyConnectionCount.incrementAndGet();
            }
            this.poolingIndex = 0;
            //
            this.enable = false;
            this.notEmpty.signalAll();
            //
            this.closed = true;
            this.closeTimeMillis = System.currentTimeMillis();

        } finally {
            this.lock.unlock();
        }

    }

    /**
     * 抛弃连接，不进行回收，而是抛弃
     *
     * @param connection
     */
    public void discardConnection(Connection connection) {
        JdbcUtils.close(connection);
        this.lock.lock();
        try {
            this.activeCount--;
            this.discardCount++;

            if (this.activeCount + this.poolingIndex <= this.minIdle) {
                createConnectionTask();
            }
        } finally {
            this.lock.unlock();
        }
    }

    private ConnectionContext getConnectionContext(long maxWaitTimeMillis) throws SQLException {
        maxWaitTimeMillis = 10;
        if (this.closed) {
            this.connecteExceptionCount.incrementAndGet();
            throw new SQLException("abstractConnectionContexts already closed at " + new Date(closeTimeMillis));
        }

        if (!this.enable) {
            this.connecteExceptionCount.incrementAndGet();
            throw new SQLException();
        }

        final long maxWaitTimeNanos = TimeUnit.MILLISECONDS.toNanos(maxWaitTimeMillis);
        final int maxWaitNotEmptyThreadCount = getMaxWaitNotEmptyThreadCount();

        SharedConnection sharedConnection;
        try {
            logger.info("lock");
            this.lock.lockInterruptibly();
        } catch (InterruptedException e) {
            this.connecteExceptionCount.incrementAndGet();
            throw new RuntimeException("interrupt", e);
        }

        try {
            if (maxWaitNotEmptyThreadCount > 0) {
                if (this.notEmptyWaitThreadCount >= maxWaitNotEmptyThreadCount) {
                    this.connecteExceptionCount.incrementAndGet();
                    throw new SQLException("maxWaitNotEmptyThreadCount " + maxWaitNotEmptyThreadCount + ", current wait Thread count " + lock.getQueueLength());
                }
            }

            this.connectCount++;
            logger.info("takeLast:maxWaitTimeNanos" + maxWaitTimeNanos);
            if (maxWaitTimeNanos > 0) {
                sharedConnection = takeLast(maxWaitTimeNanos);
            } else {
                sharedConnection = takeLast();
            }

            if (sharedConnection != null) {
                this.activeCount++;

            }
        } catch (InterruptedException e) {
            this.connecteExceptionCount.incrementAndGet();
            throw new RuntimeException(e.getMessage(), e);
        } catch (SQLException e) {
            this.connecteExceptionCount.incrementAndGet();
            throw e;
        } finally {
            this.lock.unlock();
        }

        if (sharedConnection == null) {
            long waitNanos = waitTimeNanos.get();

            StringBuilder sb = new StringBuilder();
            sb.append("wait millis ")//
                    .append(waitNanos / (1000 * 1000))//
                    .append(", active ").append(this.activeCount)//
                    .append(", maxActive ").append(this.maxActive)//
                    .append(", creating ").append(this.creatingCount.get())//
            ;

            String errorMessage = sb.toString();

            if (this.exceptionWhenCreateConntection != null) {
                throw new RuntimeException(errorMessage, this.exceptionWhenCreateConntection);
            } else {
                throw new RuntimeException(errorMessage);
            }
        }

        ConnectionContext connectionContext = new ConnectionContext(sharedConnection);
        return connectionContext;
    }


    SharedConnection takeLast() throws InterruptedException, SQLException {
        try {
            while (this.poolingIndex == 0) {
                logger.info("createConnectionTask()");
                createConnectionTask();
                //
                if (this.failFast && this.failContinuous.get()) {
                    throw new RuntimeException(this.exceptionWhenCreateConntection);
                }
                //
                this.notEmptyWaitThreadCount++;
                if (this.notEmptyWaitThreadCount > this.notEmptyWaitThreadCountPeak) {
                    this.notEmptyWaitThreadCountPeak = this.notEmptyWaitThreadCount;
                }
                try {
                    this.notEmpty.await();// 等,且暂时释放锁
                } finally {
                    this.notEmptyWaitThreadCount--;
                }
                //
                if (!this.enable) {
                    this.connecteExceptionCount.incrementAndGet();
                    throw new SQLException();
                }
            }
        } catch (InterruptedException ie) {
            this.notEmpty.signal(); // propagate to non-interrupted thread

            throw ie;
        }
        // 锁如何保证
        decrementPoolingIndex();
        SharedConnection sharedConnection = this.poolingSharedConnections[this.poolingIndex];// last
        this.poolingSharedConnections[this.poolingIndex] = null;
        return sharedConnection;
    }

    private SharedConnection takeLast(long maxWaitTimeNanos) throws InterruptedException, SQLException {
        this.waitTimeNanos.set(maxWaitTimeNanos);
        /**
         * 估计
         */
        long left = maxWaitTimeNanos;
        for (; ; ) {
            if (this.poolingIndex == 0) {
                logger.info("createConnectionTask(...)");
                createConnectionTask();

                /**
                 * 继续失败
                 */
                if (this.failFast && this.failContinuous.get()) {
                    throw new RuntimeException(this.exceptionWhenCreateConntection);
                }

                if (left <= 0) {
                    this.waitTimeNanos.set(maxWaitTimeNanos - left);
                    return null;
                }

                this.notEmptyWaitThreadCount++;
                if (this.notEmptyWaitThreadCount > this.notEmptyWaitThreadCountPeak) {
                    this.notEmptyWaitThreadCountPeak = this.notEmptyWaitThreadCount;
                }

                try {
                    long allAllowLeft = left;
                    left = this.notEmpty.awaitNanos(left);// 等,且暂时释放锁

                    this.notEmptyWaitTimeNanos += (allAllowLeft - left);

                    if (!this.enable) {
                        this.connecteExceptionCount.incrementAndGet();
                        throw new SQLException();
                    }
                } catch (InterruptedException ie) {
                    this.notEmpty.signal();
                    throw ie;
                } finally {
                    this.notEmptyWaitThreadCount--;
                }

                if (this.poolingIndex == 0) {
                    if (left > 0) {
                        continue;
                    }
                    this.waitTimeNanos.set(maxWaitTimeNanos - left);
                    return null;
                }
            }
            //
            decrementPoolingIndex();
            SharedConnection sharedConnection = this.poolingSharedConnections[this.poolingIndex];
            this.poolingSharedConnections[this.poolingIndex] = null;
            return sharedConnection;
        }
    }

    /**
     * 该方法是锁内
     */
    boolean putLast(SharedConnection sharedConnection, long lastActiveTimeMillis) {
        logger.info("put last");
        if (sharedConnection == null) {
            throw new NullPointerException(SharedConnection.class.getSimpleName());
        }
        if (this.poolingIndex >= this.maxActive) {
            return false;
        }

        sharedConnection.setLastActiveTimeMillis(lastActiveTimeMillis);
        this.poolingSharedConnections[this.poolingIndex] = sharedConnection;
        incrementPoolingIndex();
        if (this.poolingIndex > this.poolingPeak) {
            this.poolingPeak = this.poolingIndex;
            this.poolingPeakTimeMillis = lastActiveTimeMillis;
        }
        //
        this.notEmpty.signal();//可以取了
        return true;
    }

    private final void incrementPoolingIndex() {
        this.poolingIndex++;
    }

    private final void decrementPoolingIndex() {
        this.poolingIndex--;
    }

    public boolean put(Connection connection) {
        SharedConnection sharedConnection = null;
        try {
            sharedConnection = new SharedConnection(ConnectionContexts.this, connection);
            return put(sharedConnection);
        } catch (SQLException ex) {
            ex.printStackTrace();
            // 设置失败
            this.lock.lock();
            try {
                if (this.scheduledExecutorService != null) {
                    this.creatingConnectionTaskCount--;
                }
            } finally {
                this.lock.unlock();
            }

            return false;
        }

    }

    private boolean put(SharedConnection sharedConnection) {
        this.lock.lock();
        try {
            if (this.poolingIndex >= this.maxActive) {
                return false;
            }
            //
            this.poolingSharedConnections[this.poolingIndex] = sharedConnection;
            incrementPoolingIndex();// 待用...机制

            if (this.poolingIndex > this.poolingPeak) {
                this.poolingPeak = this.poolingIndex;
                this.poolingPeakTimeMillis = System.currentTimeMillis();
            }
            // 非空了
            this.notEmpty.signal();
            if (this.scheduledExecutorService != null) {
                this.creatingConnectionTaskCount--;// 任务减少
                //
                if (this.poolingIndex + this.creatingConnectionTaskCount < this.notEmptyWaitThreadCount //
                        && //
                        this.activeCount + this.poolingIndex + this.creatingConnectionTaskCount < this.maxActive) {//
                    createConnectionTask();
                }
            }
        } finally {
            this.lock.unlock();
        }
        return true;
    }

    public int removeAbandonedPooledConnectionList() {
        logger.info("移除废弃项目");
        int count = 0;

        long timeNanos = System.nanoTime();

        List<ConnectionContext> connectionContextList = new ArrayList<ConnectionContext>();

        this.activePooledConnectionToObjectMapLock.lock();
        try {
            Iterator<ConnectionContext> iterator = this.activeConnectionContextToObjectMap.keySet().iterator();
            while (iterator.hasNext()) {
                ConnectionContext connectionContext = iterator.next();
                if (connectionContext.isRunning()) {
                    continue;
                }
                long $ = (timeNanos - connectionContext.getConnectedTimeNano()) / (1000 * 1000);
                if ($ >= this.removeAbandonedTimeoutMillis) {
                    iterator.remove();
                    connectionContext.setTraceEnable(false);
                    connectionContextList.add(connectionContext);
                }
            }
        } finally {
            this.activePooledConnectionToObjectMapLock.unlock();
        }
        if (connectionContextList.size() > 0) {
            for (ConnectionContext connectionContext : connectionContextList) {
                final ReentrantLock lock = connectionContext.lock;
                lock.lock();
                try {
                    if (connectionContext.isDisable()) {//?
                        continue;
                    }
                } finally {
                    lock.unlock();
                }
                connectionContext.close();
                connectionContext.abandoned = true;
                this.removeAbandonedCount++;
                count++;

            }
        }

        return count;
    }

    /**
     * 销毁数据库连接任务使用
     */
    public void shrink(boolean checkTime, boolean keepAlive) {
        try {
            this.lock.lockInterruptibly();
        } catch (InterruptedException e) {
            return;
        }

        int evictCount = 0;
        int keepAliveCount = 0;
        try {
            if (!this.inited) {
                return;
            }

            final int poolingIndex_minIdle_num = this.poolingIndex - this.minIdle;// 只保留最小
            final long currentTimeMillis = System.currentTimeMillis();
            // 从左到右
            for (int i = 0; i < this.poolingIndex; ++i) {
                SharedConnection sharedConnection = this.poolingSharedConnections[i];
                //
                if (checkTime) {
                    if (this.maxAliveTimeOutTimeMillis > 0) {
                        // [1]
                        if (currentTimeMillis - sharedConnection.startTimeMillis > this.maxAliveTimeOutTimeMillis) {
                            this.evictSharedConnections[evictCount++] = sharedConnection;
                            continue;
                        }
                    }
                    // [2]
                    long idleTimeMillis = currentTimeMillis - sharedConnection.lastActiveTimeMillis;
                    if (idleTimeMillis < this.minEvictableIdleTimeMillis) {
                        break;
                    }
                    // [3]
                    if (i < poolingIndex_minIdle_num) {
                        this.evictSharedConnections[evictCount++] = sharedConnection;
                        // [4]
                    } else if (idleTimeMillis > this.maxEvictableIdleTimeMillis) {
                        this.evictSharedConnections[evictCount++] = sharedConnection;
                        //
                    } else if (keepAlive) {
                        this.keepAliveSharedConnections[keepAliveCount++] = sharedConnection;
                    } else {
                        //
                    }
                } else {// !checkTime
                    if (i < poolingIndex_minIdle_num) {
                        this.evictSharedConnections[evictCount++] = sharedConnection;
                    } else {
                        break;
                    }
                }
            }

            int removeCount = evictCount + keepAliveCount;
            if (removeCount > 0) {
                int startIndex = removeCount;
                int length = this.poolingIndex - removeCount;
                System.arraycopy(//
                        this.poolingSharedConnections, startIndex,//
                        this.poolingSharedConnections, 0, //
                        length);
                Arrays.fill(this.poolingSharedConnections,//
                        this.poolingIndex - removeCount,//
                        this.poolingIndex,//
                        null);//
                this.poolingIndex -= removeCount;
            }
            setKeepAliveCheckCount(getKeepAliveCheckCount() + keepAliveCount);
        } finally {
            this.lock.unlock();
        }

        if (evictCount > 0) {
            for (int i = 0; i < evictCount; ++i) {
                SharedConnection sharedConnection = evictSharedConnections[i];
                Connection connection = sharedConnection.getConnection();
                JdbcUtils.close(connection);
                this.destroyConnectionCount.incrementAndGet();
            }
            Arrays.fill(this.evictSharedConnections, null);
        }

        if (keepAliveCount > 0) {
            for (int i = keepAliveCount - 1; i >= 0; --i) {
                SharedConnection sharedConnection = this.keepAliveSharedConnections[i];
                Connection connection = sharedConnection.getConnection();
                boolean validate = false;
                try {
                    this.validateConnection(connection);
                    validate = true;
                } catch (Exception e) {

                }
                if (validate) {
                    sharedConnection.setLastActiveTimeMillis(System.currentTimeMillis());
                    put(sharedConnection);
                } else {
                    JdbcUtils.close(connection);
                }
            }
            Arrays.fill(this.keepAliveSharedConnections, null);
        }
    }

    public boolean isFull() {
        lock.lock();
        try {
            return this.poolingIndex + this.activeCount >= this.maxActive;
        } finally {
            lock.unlock();
        }
    }

    //has in lock
    private void createConnectionTask() {
        if (this.scheduledExecutorService == null) {
            this.empty.signal();//
            return;
        }
        // 两个标准
        // 创建线程的任务数>=最大允许创建的任务数
        if (this.creatingConnectionTaskCount >= this.maxAllowCreateConnectionTaskCount) {
            return;
        }
        // 使用数+池子中的数+正在创建的任务数>=最大允许的连接数
        if (this.activeCount + this.poolingIndex + this.creatingConnectionTaskCount >= this.maxActive) {
            return;
        }

        this.creatingConnectionTaskCount++;
        CreateConnectionContextTask createConnectionContextTask = new CreateConnectionContextTask(this);
        this.createConnectionTaskFuture = this.scheduledExecutorService.submit(createConnectionContextTask);// Executor
    }

    public void handleConnectionException(ConnectionContext connectionContext, Exception exception) throws SQLException {
        final SharedConnection sharedConnection = connectionContext.getSharedConnection();

        this.errorCount.incrementAndGet();
        this.lastError = exception;
        this.lastErrorTimeMillis = System.currentTimeMillis();

        if (exception instanceof SQLException) {
            SQLException sqlException = (SQLException) exception;
            if (MySqlExceptionSorter.isExceptionFatal(sqlException)) {
                if (connectionContext.isTraceEnable()) {
                    this.activePooledConnectionToObjectMapLock.lock();
                    try {
                        if (connectionContext.isTraceEnable()) {
                            this.activeConnectionContextToObjectMap.remove(connectionContext);
                            connectionContext.setTraceEnable(false);
                        }
                    } finally {
                        this.activePooledConnectionToObjectMapLock.unlock();
                    }
                }

                boolean requireDiscard = false;
                final ReentrantLock lock = connectionContext.lock;
                lock.lock();
                try {
                    if ((!connectionContext.isClosed()) || !connectionContext.isDisable()) {
                        sharedConnection.setDiscard(true);
                        connectionContext.disable(exception);
                        requireDiscard = true;
                    }
                } finally {
                    lock.unlock();
                }

                if (requireDiscard) {
                    this.discardConnection(sharedConnection.getConnection());
                    sharedConnection.setDiscard(true);
                }
            }

            throw sqlException;
        } else {
            throw new RuntimeException("Error", exception);
        }
    }

}
